/*
 * Copyright 2014 Щекотова Ангелина Александровна.
 * Этот файл — часть J4JClientServer Framework
 * J4JClientServer Framework - свободная программа: вы можете перераспространять ее и/или изменять
 * ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде, в каком
 * она была опубликована Фондом свободного программного обеспечения; либо
 * версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
 *
 * J4JClientServer Framework распространяется в надежде, что она будет полезной,
 * но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
 * или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
 * общественной лицензии GNU.
 *
 * Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
 * вместе с этой программой. Если это не так, см.
 * <http://www.gnu.org/licenses/>.
 */

package ru.java4java.security;

import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.LinkedList;

public class BasicCipher {

    private final static BigInteger   one    = BigInteger.ONE;
    private final static SecureRandom random = new SecureRandom();

    private final LinkedList<BasicCipher> ciphers = new LinkedList<>();

    private int bitLength = 1024;

    private BigInteger privateKey;
    private BigInteger publicKey;
    private BigInteger modulus;

    public void init(int N) {
        BigInteger p = BigInteger.probablePrime(N >> 1, random);
        BigInteger q = BigInteger.probablePrime(N >> 1, random);
        BigInteger phi = (p.subtract(one)).multiply(q.subtract(one)); // (p - 1) * (q - 1)
        modulus = p.multiply(q);
        privateKey = publicKey.modInverse(phi);
    }

    public void setPrivateKey(BigInteger privateKey) { this.privateKey = privateKey; }

    public void setPublicKey(BigInteger publicKey) { this.publicKey = publicKey; }

    public void setModulus(BigInteger modulus) { this.modulus = modulus; }

    public BigInteger getPrivateKey() { return privateKey; }

    public BigInteger getPublicKey() { return publicKey; }

    public BigInteger getModulus() { return modulus; }

    public void setBitLength(int bitLength) { this.bitLength = bitLength; }

    public int getBitLength() { return this.bitLength; }

    // generate an N-bit (roughly) public and private key
    private BasicCipher() {
        publicKey  = new BigInteger("65537");     // common value in practice = 2^16 + 1
    }

    public BasicCipher getInstance() {
        BasicCipher basicCipher = null;
        if (ciphers.isEmpty()) {
            basicCipher = new BasicCipher();
            basicCipher.init(bitLength);
        } else {
            basicCipher = ciphers.removeLast();
        }
        return basicCipher;
    }

    public void generateInstances(int bitLength, int count) {
        for (int i = 0; i < count; i++) {
            BasicCipher cipher = new BasicCipher();
            cipher.init(bitLength);
            ciphers.push(cipher);
        }
    }

    public void addCipher(BasicCipher cipher) {
        ciphers.push(cipher);
    }

    public BigInteger encrypt(BigInteger message) {
        return message.modPow(publicKey, modulus);
    }

    public BigInteger decrypt(BigInteger encrypted) {
        return encrypted.modPow(privateKey, modulus);
    }

    public String toString() {
        return "public  = \"" + publicKey  + "\", modulus = \"" + modulus + "\"";
    }

}
